package com.fxu.framework.biz.aop;

import cn.hutool.json.JSONConfig;
import cn.hutool.json.JSONUtil;
import com.fxu.framework.biz.ano.Logger;
import com.fxu.framework.core.base.Result;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.Method;

/**
 * 打印日志
 * @author fangxu
 * @version 1.0.0
 */
@Slf4j
@Aspect
@Component
public class LoggerAspect {

    @Pointcut("@annotation(com.fxu.framework.biz.ano.Logger)")
    public void pointcut() {
    }

    @Around("pointcut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {

        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        Logger logger = method.getAnnotation(Logger.class);

        // 构建唯一的 logger name： com.example.Class.methodName
        String name = method.getDeclaringClass().getName() + "." + method.getName();
        String args = getArgs(joinPoint.getArgs());
        if (logger.back() || logger.param()) {
            log.info("---> {}({})", name, args);
        }
        long time = System.currentTimeMillis();
        Object back = joinPoint.proceed();
        time = System.currentTimeMillis() - time;
        if (!logger.back() && logger.param() && logger.time()) {
            log.info("<--- {}, 耗时: {}ms", name, time);
        } else if (!logger.back() && !logger.param() && logger.time()) {
            log.info("{}({}), 耗时: {}ms", name, args, time);
        }
        if (logger.back()) {
            String backStr = back == null ? "" : (back instanceof Result ? JSONUtil.toJsonStr(back) : back.toString());
            log.info("<--- {}, 耗时: {}ms, 返回:\n{}", name, time, backStr);
        }
        return back;
    }

    // 配置异常通知
    @AfterThrowing(pointcut = "pointcut()", throwing = "e")
    public void afterThrowing(JoinPoint joinPoint, Throwable e) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        Logger logger = method.getAnnotation(Logger.class);
        String loggerName = method.getDeclaringClass().getName() + "." + method.getName();
        String args = getArgs(joinPoint.getArgs());
        if (logger.param() || logger.back()) {
            log.info("{}({}), e={}", loggerName, args, getStackTrace(e));
        }
    }

    public static String getArgs(Object[] args) {
        String string = JSONUtil.toJsonStr(args, JSONConfig.create().setIgnoreNullValue(false));
        if (string.startsWith("[") && string.endsWith("]")) {
            return string.substring(1, string.length() - 1);
        }
        return string;
    }

    public static String getStackTrace(Throwable throwable){
        StringWriter sw = new StringWriter();
        try (PrintWriter pw = new PrintWriter(sw)) {
            throwable.printStackTrace(pw);
            return sw.toString();
        }
    }
}
